package com.ayu.shortlink.admin.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson2.JSONObject;
import com.ayu.shortlink.admin.common.biz.user.UserContext;
import com.ayu.shortlink.admin.common.constant.RedisCacheConstant;
import com.ayu.shortlink.admin.common.convention.exception.ClientException;
import com.ayu.shortlink.admin.common.enums.CouponTaskSendTypeEnum;
import com.ayu.shortlink.admin.common.enums.CouponTaskStatusEnum;
import com.ayu.shortlink.admin.dao.entity.CouponTaskDO;
import com.ayu.shortlink.admin.dao.entity.VipUserDO;
import com.ayu.shortlink.admin.dao.mapper.CouponTaskMapper;
import com.ayu.shortlink.admin.dao.mapper.VipUserMapper;
import com.ayu.shortlink.admin.dto.req.CouponTaskCreateReqDTO;
import com.ayu.shortlink.admin.dto.req.CouponTaskPageQueryReqDTO;
import com.ayu.shortlink.admin.dto.req.UserExcelCreateReqDTO;
import com.ayu.shortlink.admin.dto.resp.CouponTaskPageQueryRespDTO;
import com.ayu.shortlink.admin.dto.resp.CouponTaskQueryRespDTO;
import com.ayu.shortlink.admin.dto.resp.CouponTemplateQueryRespDTO;
import com.ayu.shortlink.admin.mq.basics.event.CouponTaskExecuteEvent;
import com.ayu.shortlink.admin.mq.producer.CouponTaskExecuteProducer;
import com.ayu.shortlink.admin.service.CouponTaskService;
import com.ayu.shortlink.admin.service.CouponTemplateService;
import com.ayu.shortlink.admin.service.basics.excel.data.ExcelGenerateBaseData;
import com.ayu.shortlink.admin.service.handler.excel.RowCountListener;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RBlockingDeque;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RedissonClient;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.nio.file.Paths;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.*;

/**
 * 优惠券任务实现层
 */
@Service
@RequiredArgsConstructor
public class CouponTaskServiceImpl extends ServiceImpl<CouponTaskMapper, CouponTaskDO> implements CouponTaskService {
    private final CouponTaskMapper couponTaskMapper;
    private final VipUserMapper vipUserMapper;
    private final CouponTemplateService couponTemplateService;
    private final RedissonClient redissonClient;
    private final CouponTaskExecuteProducer couponTaskExecuteProducer;
    private final String excelPath = Paths.get("").toAbsolutePath().getParent() + "/tmp";
    private final ExecutorService executorService = new ThreadPoolExecutor(
            Runtime.getRuntime().availableProcessors(),
            Runtime.getRuntime().availableProcessors() << 1,
            60,
            TimeUnit.SECONDS,
            new SynchronousQueue<>(),
            new ThreadPoolExecutor.DiscardPolicy()
    );

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createCouponTask(CouponTaskCreateReqDTO requestParam) {
        //基本参数校验
        checkParam(requestParam);

        //保证模板信息存在
        CouponTemplateQueryRespDTO couponTemplate = couponTemplateService.findCouponTemplateById(requestParam.getCouponTemplateId());
        if(couponTemplate == null){
            throw new ClientException("优惠券信息不存在");
        }

        //避免任务丢失 先刷盘落库
        CouponTaskDO couponTaskDO = BeanUtil.copyProperties(requestParam, CouponTaskDO.class);
        couponTaskDO.setBatchId(IdUtil.getSnowflakeNextId());
        //TODO 为了方便测试 这里的操作对象设置为1 默认为1
        couponTaskDO.setOperatorId(Long.parseLong(UserContext.getUserId()));
        couponTaskDO.setShopNumber(UserContext.getShopNumber());
        couponTaskDO.setStatus(
                Objects.equals(requestParam.getSendType(), CouponTaskSendTypeEnum.IMMEDIATE.getType())
                        ? CouponTaskStatusEnum.IN_PROGRESS.getStatus()
                        : CouponTaskStatusEnum.PENDING.getStatus()
        );
        couponTaskMapper.insert(couponTaskDO);

        // 读的时间较长 所以我们异步化
        JSONObject jsonObject = JSONObject.of("fileAddress",requestParam.getFileAddress(),"couponTaskId",couponTaskDO.getId());
        executorService.execute(() -> doEasyExcelRead(jsonObject));

        // 为了防止应用宕机 将任务交到Redisson的延迟队列
        RBlockingDeque<Object> blockingDeque = redissonClient.getBlockingDeque(RedisCacheConstant.DELAY_EXECUTOR_SAFE_KEY);
        RDelayedQueue<Object> delayedQueue = redissonClient.getDelayedQueue(blockingDeque);
        delayedQueue.offer(jsonObject,30,TimeUnit.SECONDS);

        // 如果任务需要立即执行应该把消息发到RocketMQ
        if(couponTaskDO.getSendType().equals(CouponTaskSendTypeEnum.IMMEDIATE.getType())){
            CouponTaskExecuteEvent event = CouponTaskExecuteEvent.builder().
                    couponTaskId(couponTaskDO.getId()).
                    build();
            couponTaskExecuteProducer.sendMessage(event);
        }
    }


    @Override
    public CouponTaskQueryRespDTO queryCouponTask(String couponTaskId) {
        // 加UserContext.getShopNumber()是为了防止权限越界
        LambdaQueryWrapper<CouponTaskDO> queryWrapper = Wrappers.lambdaQuery(CouponTaskDO.class)
                .eq(CouponTaskDO::getShopNumber, UserContext.getShopNumber())
                .eq(CouponTaskDO::getId, couponTaskId);
        return BeanUtil.toBean(couponTaskMapper.selectOne(queryWrapper),CouponTaskQueryRespDTO.class);
    }

    @Override
    public IPage<CouponTaskPageQueryRespDTO> pageQueryCouponTask(CouponTaskPageQueryReqDTO requestParam) {
        // 加UserContext.getShopNumber()是为了防止权限越界
        LambdaQueryWrapper<CouponTaskDO> queryWrapper = Wrappers.lambdaQuery(CouponTaskDO.class)
                .eq(CouponTaskDO::getShopNumber, UserContext.getShopNumber())
                .like(StrUtil.isNotBlank(requestParam.getBatchId()),CouponTaskDO::getBatchId, requestParam.getBatchId())
                .like(StrUtil.isNotBlank(requestParam.getTaskName()),CouponTaskDO::getTaskName, requestParam.getTaskName())
                .like(StrUtil.isNotBlank(requestParam.getCouponTemplateId()),CouponTaskDO::getCouponTemplateId,requestParam.getCouponTemplateId())
                .eq(ObjectUtil.isNotEmpty(requestParam.getStatus()),CouponTaskDO::getStatus, requestParam.getStatus());

        IPage<CouponTaskDO> selectPage = couponTaskMapper.selectPage(requestParam, queryWrapper);
        return selectPage.convert(each -> BeanUtil.toBean(each, CouponTaskPageQueryRespDTO.class));
    }

    @Override
    public void createUserExcel(UserExcelCreateReqDTO requestParam) {
        if (!FileUtil.exist(excelPath)) {
            FileUtil.mkdir(excelPath);
        }
        String fileName = excelPath + "/任务推送 "+UUID.randomUUID()+"Excel.xlsx";
        //进行Excel生成
        EasyExcel.write(fileName,ExcelGenerateBaseData.class).sheet("优惠券发送列表").doWrite(getExcelData(requestParam.getShopNumber()));
    }

    //TODO 优化成分批读写
    private List<ExcelGenerateBaseData> getExcelData(String shopNumber){
        LambdaQueryWrapper<VipUserDO> queryWrapper = Wrappers.lambdaQuery(VipUserDO.class)
                .eq(VipUserDO::getShopNumber, shopNumber);

        List<VipUserDO> vipUserDOS = vipUserMapper.selectList(queryWrapper);
        if (CollUtil.isEmpty(vipUserDOS)){
            throw new ClientException("暂无Vip用户数据");
        }
        return BeanUtil.copyToList(vipUserDOS, ExcelGenerateBaseData.class);
    }

    /**
     * 参数校验 先校验重要的
     */
    private void checkParam(CouponTaskCreateReqDTO requestParam) {
        if(StrUtil.isEmpty(requestParam.getCouponTemplateId())){
            throw new ClientException("优惠券模板信息不能为空");
        }
        if(StrUtil.isEmpty(requestParam.getFileAddress())){
            throw new ClientException("优惠券Excel地址不能为空");
        }

    }

    private void doEasyExcelRead(JSONObject jsonObject){
        RowCountListener listener = new RowCountListener();
        EasyExcel.read(jsonObject.getString("fileAddress"),listener).sheet().doRead();
        int count = listener.getCount();
        CouponTaskDO couponTaskId = CouponTaskDO.builder()
                .id(jsonObject.getLong("couponTaskId"))
                .sendNum(count)
                .build();
        couponTaskMapper.updateById(couponTaskId);
    }

    @Service
    @RequiredArgsConstructor
    class RedisDelayQueueListener implements CommandLineRunner {
        private final RedissonClient redissonClient;
        private final CouponTaskMapper couponTaskMapper;

        @Override
        public void run(String... args) throws Exception {
            Executors.newSingleThreadExecutor(
                    runnable ->{
                        Thread thread = new Thread(runnable);
                        thread.setName("delay_queue_listener");
                        thread.setDaemon(true);
                        return thread;
                    }).execute(() -> {
                        RBlockingDeque<JSONObject> blockingDeque = redissonClient.getBlockingDeque(RedisCacheConstant.DELAY_EXECUTOR_SAFE_KEY);
                        for (;;){
                            try {
                                JSONObject object = blockingDeque.take();
                                if(ObjectUtil.isEmpty(object)){
                                    continue;
                                }
                                CouponTaskDO couponTemplateId = couponTaskMapper.selectById(object.getLong("couponTaskId"));
                                if(couponTemplateId.getSendNum() == null) {
                                    doEasyExcelRead(object);
                                }
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        }
            });
        }
    }
}
